Skip to content

Conversation

@OlenaYefymenko
Copy link

This patch provides a new configuration — .coveragerc.toml . Considering that many projects have switched to toml configurations, this change offers a more flexible approach to manage coverage settings.

Resolves #1643

Comment on lines +574 to 648
(".coveragerc.toml", True, False),
("setup.cfg", False, False),
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the priority of the new config properly determined in the config_files_to_try function?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good.

Comment on lines 67 to 80
@pytest.mark.parametrize("filename", ["pyproject.toml", ".coveragerc.toml"])
def test_toml_config_file(self, filename) -> None:
# A pyproject.toml and coveragerc.toml will be read into the configuration.
self.make_file(filename, """\
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the parameterization implemented appropriately, similar to pyproject.toml?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes.

Comment on lines +836 to +1039
def test_coveragerc_toml_priority(self) -> None:
"""Test that .coveragerc.toml has priority over pyproject.toml."""
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is testing priority only over pyproject.toml in the test_coveragerc_toml_priority test sufficient?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes.

@OlenaYefymenko
Copy link
Author

@nedbat,
I've opened PR with the improvements, but I have a few doubts about my implementation. I've added comments in the code. I'd really appreciate your feedback on whether this approach works well.
Thank you!

@nedbat nedbat changed the title 🌐 Support .coveragerc.toml for configuration Support .coveragerc.toml for configuration Apr 16, 2025
Copy link
Member

@nedbat nedbat left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One change needed.

Comment on lines +574 to 648
(".coveragerc.toml", True, False),
("setup.cfg", False, False),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good.

Comment on lines 67 to 80
@pytest.mark.parametrize("filename", ["pyproject.toml", ".coveragerc.toml"])
def test_toml_config_file(self, filename) -> None:
# A pyproject.toml and coveragerc.toml will be read into the configuration.
self.make_file(filename, """\
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes.

Comment on lines +836 to +1039
def test_coveragerc_toml_priority(self) -> None:
"""Test that .coveragerc.toml has priority over pyproject.toml."""
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes.

""")
with mock.patch.object(coverage.tomlconfig, "has_tomllib", False):
msg = "Can't read 'pyproject.toml' without TOML support"
msg = "Can't read '{filename}' without TOML support"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like tests are failing because this needs to be an f-string.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you. I will fix this error in the next commits. I run tox with version 3.12 locally, and I didn't see this error.

@nedbat
Copy link
Member

nedbat commented Apr 16, 2025

Thanks for doing this. There are some other changes needed. Let me know which you want to do, and which I should do:

  • Update the documentation
  • Add an entry to CHANGES.rst
  • Add your name to CONTRIBUTORS.txt

@OlenaYefymenko
Copy link
Author

OlenaYefymenko commented Apr 17, 2025

Thanks for doing this. There are some other changes needed. Let me know which you want to do, and which I should do:

  • Update the documentation
  • Add an entry to CHANGES.rst
  • Add your name to CONTRIBUTORS.txt

Yes, I will add the changes

@OlenaYefymenko OlenaYefymenko marked this pull request as draft May 7, 2025 21:32
@OlenaYefymenko OlenaYefymenko force-pushed the support-configuration-coverage-toml branch from 4018790 to 673b0c0 Compare June 15, 2025 23:16
@OlenaYefymenko OlenaYefymenko force-pushed the support-configuration-coverage-toml branch from 673b0c0 to c584e0d Compare July 20, 2025 13:52
Copy link
Contributor

@webknjaz webknjaz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Plz revert all the formatting changes. I marked some, find the rest.

@OlenaYefymenko OlenaYefymenko force-pushed the support-configuration-coverage-toml branch from 6bee75f to 7565729 Compare August 18, 2025 22:17
@OlenaYefymenko OlenaYefymenko force-pushed the support-configuration-coverage-toml branch from 7565729 to 2558209 Compare August 31, 2025 22:19
CHANGES.rst Outdated
.. _pull 1952: https://github.com/nedbat/coveragepy/pull/1952


.. start-releases
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Invalid conflict resolution?

doc/config.rst Outdated
[html]
directory = coverage_html_report
""",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stray edit?

"""\
@pytest.mark.parametrize("filename", ["pyproject.toml", ".coveragerc.toml"])
def test_toml_config_file(self, filename: str) -> None:
# A pyproject.toml and coveragerc.toml will be read into the configuration.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
# A pyproject.toml and coveragerc.toml will be read into the configuration.
# A pyproject.toml and .coveragerc.toml will be read into the configuration.

def test_toml_parse_errors(self, bad_config: str, msg: str) -> None:
@pytest.mark.parametrize("filename", ["pyproject.toml", ".coveragerc.toml"])
@pytest.mark.parametrize("bad_config, msg", [
("[tool.coverage.run]\ntimid = \"maybe?\"\n", r"maybe[?]"),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A lot of reformatting?

assert cov.config.xml_output == "/Users/me/somewhere/xml.out"
assert cov.config.exclude_list == ["~/data.file", "~joe/html_dir"]
assert cov.config.paths == {"mapping": ["/Users/me/src", "/Users/joe/source"]}
assert cov.config.paths == {'mapping': ['/Users/me/src', '/Users/joe/source']}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

?

self.make_file(
"pyproject.toml",
"""\
self.make_file("pyproject.toml", """\
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

?

This adds .coveragerc.toml to the list of configuration files that
coverage.py will automatically search for, with higher priority
than pyproject.toml but lower than .coveragerc.
Converts existing pyproject.toml tests to be parametrized to also
test .coveragerc.toml functionality, ensuring both TOML config
formats work identically.
This test ensures that when both .coveragerc.toml and pyproject.toml
are present, .coveragerc.toml takes precedence as intended.
@OlenaYefymenko OlenaYefymenko force-pushed the support-configuration-coverage-toml branch from 05be667 to 0a508bf Compare October 19, 2025 22:47
@OlenaYefymenko
Copy link
Author

I'm in progress - some tests have failed, I'm going to fix them

# A pyproject.toml file will be read into the configuration.
@pytest.mark.parametrize("filename", ["pyproject.toml", ".coveragerc.toml"])
def test_toml_config_file(self, filename) -> None:
# A pyproject.toml and .coveragerc.toml file will be read into the configuration.
Copy link
Contributor

@webknjaz webknjaz Oct 20, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, not both, just one of them, right?

Suggested change
# A pyproject.toml and .coveragerc.toml file will be read into the configuration.
# A pyproject.toml or .coveragerc.toml file will be read into the configuration.

OlenaYefymenko and others added 5 commits October 22, 2025 00:36
Co-authored-by: 🇺🇦 Sviatoslav Sydorenko (Святослав Сидоренко) <[email protected]>
Co-authored-by: 🇺🇦 Sviatoslav Sydorenko (Святослав Сидоренко) <[email protected]>
@ofek
Copy link

ofek commented Nov 10, 2025

I'm very excited to start using this feature, thanks for the effort here @OlenaYefymenko!!!

@nedbat
Copy link
Member

nedbat commented Nov 10, 2025

@webknjaz @OlenaYefymenko What is left to do with this PR?

@webknjaz
Copy link
Contributor

@nedbat just the change log move. Olena was going to mark is as ready just about now.

@OlenaYefymenko OlenaYefymenko marked this pull request as ready for review November 10, 2025 20:51
@OlenaYefymenko
Copy link
Author

OlenaYefymenko commented Nov 10, 2025

@webknjaz @OlenaYefymenko What is left to do with this PR?

@nedbat the pull request is ready for review. Please let me know if anything needs to be edited.
Thank you for your help and patience

@nedbat
Copy link
Member

nedbat commented Nov 11, 2025

Thanks for doing this, and thanks for persisting.

With these changes, the .coverage.toml file would have entries like:

[tool.coverage.run]
branch = true

but wouldn't we want this?:

[run]
branch = true

I'd like to make things seem natural, and I also like to give people options, so it's hard to strike the right balance. When I read (for example) pytest's explanation of their configuration rules my head starts to spin.

@webknjaz
Copy link
Contributor

but wouldn't we want this?:

[run]
branch = true

I've seen both startegies in the wild and I'm leaning towards not having differences with pyproject.toml.

For example, Towncrier has [tool.towncrier] in towncrier.toml and in cython-lint, I asked for the same [tool.cython-lint] in .cython-lint.toml. I also insisted on this when we were getting a PR in pip-tools — you'll have [tool.pip-tools] in .pip-tools.toml. PyLint does the same. Pyright seems to have implemented parsing custom .toml files as pyproject.toml (microsoft/pyright#7997) but it's unclear if it picks up a .pyright.toml as the default automatically. Poetry has a poetry.toml for the tool configuration that isn't PEP 621 packaging metadata: https://python-poetry.org/docs/configuration/#local-configuration. For Black, it's a bit confusing but it mentions .black files in user-global locations + passing a custom name on the command line: https://black.readthedocs.io/en/stable/usage_and_configuration/the_basics.html#where-black-looks-for-the-file — they are likely TOML with the same structure too, but I haven't checked.
isort doesn't really make it clear how it parses a custom config file: https://pycqa.github.io/isort/docs/configuration/config_files#custom-config-files.

I did mention this opinion in the original issue, FTR: #1643.

My reasoning for this is that it's easier to migrate/move things between pyproject.toml and a .{tool-name}.toml for the end-users, UX-wise. And it's more straightforward when there's no differences with custom filenames: what if one passes a --config=an-arbitrary-file-name-that-is-not-pyproject.toml — which way we'd want to parse it? If they are the same, the same parser is used and there's no confusion between adding or not adding section prefixes. But if there's differences, this would incur more maintenance burden for both project maintenance and end-users.

I'd like to make things seem natural, and I also like to give people options, so it's hard to strike the right balance. When I read (for example) pytest's explanation of their configuration rules my head starts to spin.

Right. I missed the PR it when was being implemented.. I'd ask for a prefix if I'd seen it, but there's no going back I guess.

Ruff has the section/prefix removed, OTOH: https://docs.astral.sh/ruff/configuration/#__tabbed_1_2. It does save a few keystrokes but I'd rather keep it uniform.
Hatch also has unprefixed sections: https://hatch.pypa.io/latest/config/environment/overview/#environment-configuration.

@ofek
Copy link

ofek commented Nov 11, 2025

Please, please do not support the tool.coverage prefix in this new file.

  1. Not matching the structure of the existing .coveragerc dedicated config file violates the principle of least surprise.
  2. It's unnatural for design decisions of individual projects to be influenced by the language's packaging configuration. For example, Cargo.toml files support the same type of table called package.metadata that is ignored and is meant for tools. I've never seen config for projects that happen to be written in Rust, or are for Rust development, use that prefix.
  3. We should take note of what developers use the most rather than noting precedent for keeping the prefix. Here are examples of popular projects (some already mentioned) that support dedicated config files without a prefix: pytest, Ruff, uv, Hatch, tox, etc. It's worth noting as well that projects receiving more maintenance tend to favor this approach to decouple from historical packaging nuances and provide a better UX.
  4. Tools that compute a dependency graph like uv cannot distinguish distribution-affecting pyproject.toml changes from others because it's all about the file hash (same as other build systems like Bazel). In this case, commands like uv run must rebuild the package whenever changes occur and it becomes advantageous to extract configuration that doesn't affect the build artifacts. This is particularly cumbersome for projects with extension modules: one change to coverage measurement means waiting dozens of seconds or more unnecessarily.

@OlenaYefymenko Would you be open to such a change in this PR?

@webknjaz
Copy link
Contributor

That's up to @nedbat to decide, then.

@ofek
Copy link

ofek commented Nov 15, 2025

Oh, for sure! I was just asking if @OlenaYefymenko would personally be fine with that approach.

@OlenaYefymenko
Copy link
Author

Would you be open to such a change in this PR?

@ofek thank you for the detailed explanation. Yes, I will make the necessary changes. I'm just waiting for the final decision from @nedbat

@nedbat
Copy link
Member

nedbat commented Nov 16, 2025

I hope this doesn't seem like a cop-out: I think there are good arguments for both side, and I'd like to support both prefixed and un-prefixed settings, aiming for the least surprise. The only complexity here is that in pyproject.toml, we need to insist on the prefix. We already have precedent for this.

In tomlconfig.py we have this:

prefixes = ["tool.coverage."]
for prefix in prefixes:
    real_section = prefix + section

This code looks silly (why iterate over a fixed one-element list?), but it's that way because config.py has this:

class HandyConfigParser(configparser.ConfigParser):
    def __init__(self, our_file: bool) -> None:
        """Create the HandyConfigParser.

        `our_file` is True if this config file is specifically for coverage,
        False if we are examining another config file (tox.ini, setup.cfg)
        for possible settings.
        """
        self.section_prefixes = ["coverage:"]
        if our_file:
            self.section_prefixes.append("")

That code is there because we insist on the "coverage:" prefix in tox.ini and setup.cfg, but are flexible in .coveragerc or in an explicitly specified config file.

It would take just adding two lines to tomlconfig.py to let it read either style of section, while being strict about the sections in pyproject.toml.

To avoid over-complicating the docs, I'd mention the flexibility of section names in the opening paragraphs about syntax, but in the examples, only show the fully prefixed syntax. Maybe the tab name could be just "toml"?

I hope everyone will be happy with this approach.

@webknjaz
Copy link
Contributor

Sounds reasonable.

@ofek
Copy link

ofek commented Nov 17, 2025

I think it would be good to mention the possibility of either naming in the opening but I disagree about showing the fully prefixed syntax in the examples because then that would encourage folks to copy/paste that forever. I would imagine that there would be a new tab that comes first here:

image

This new tab would have the name .coveragerc.toml as implemented in this PR and the second tab would be pyproject.toml. At this point I think two options would be reasonable for the INI-formatted files:

  1. Maintain their tabs and add a note to discourage their use either in the title or in the content after clicking.
  2. Extract them into a separate collapsible section for legacy files.

Making the new standalone TOML file the one that is shown by default as well as its syntax would then maintain complete compatibility with the current documentation that starts the sections with the name of the options and no prefix:

image

This would also match strategy of the pytest config docs placing what users are likely to prefer first on the page. I won't go into it here but there is a strong preference amongst developers for TOML, YAML, etc. over INI-style partly due to how uncommon it is nowadays and partly due to the ease of parsing such configuration with tooling written in other languages (never in the standard library).

In any case even if there is disagreement here about the preferred file format I think the prefix should not be prominent when, like I mentioned, it really is just an artifact of a packaging quirk in our specific language. I'm heavily involved in Python packaging now but I wasn't around when that was happening, so I'm sorry about that.

@webknjaz
Copy link
Contributor

Agreed, a dedicated tab sounds good. That's sounds closer to what the original feature request was after.

@nedbat
Copy link
Member

nedbat commented Nov 17, 2025

I appreciate the thought and effort you are putting into this. Four tabs sounds reasonable. I'd prefer this order, and I've snuck in a comment to help people understand the flexibility in the TOML syntax:
image

This patch does it:

diff --git a/doc/cog_helpers.py b/doc/cog_helpers.py
index 1ff4e1aa..be681074 100644
--- a/doc/cog_helpers.py
+++ b/doc/cog_helpers.py
@@ -76,7 +76,7 @@ def show_configs(ini, toml):
     The equivalence is checked for accuracy, and the process fails if there's
     a mismatch.

-    A three-tabbed box will be produced.
+    A four-tabbed box will be produced.
     """
     ini, ini_vals = _read_config(ini, "covrc")
     toml, toml_vals = _read_config(toml, "covrc.toml")
@@ -89,11 +89,15 @@ def show_configs(ini, toml):
             )

     ini2 = re.sub(r"(?m)^\[", "[coverage:", ini)
+    toml2 = "# You can also use sections like [tool.coverage.run]\n"
+    toml2 += toml.replace("[tool.coverage.", "[")
+
     print()
     print(".. tabs::\n")
     for name, syntax, text in [
+        ("pyproject.toml", "toml", toml),
         (".coveragerc", "ini", ini),
-        (".coveragerc.toml or pyproject.toml", "toml", toml),
+        (".coverage.toml", "toml", toml2),
         ("setup.cfg or tox.ini", "ini", ini2),
     ]:
         print(f"    .. code-tab:: {syntax}")

BTW, I hadn't really noticed until now, but this PR names the file .coveragerc.toml, when it should be .coverage.toml.

We can work on the exact wording of the opening syntax section when the time comes.

Also, it's impossible to account for all the syntax possibilities. TOML is flexible enough that either of these will work in pyproject.toml:

[tool.coverage.run]
branch = true
core = "sysmon"

[tool.coverage]
run = { branch = true, core = "sysmon" }

@webknjaz
Copy link
Contributor

BTW, I hadn't really noticed until now, but this PR names the file .coveragerc.toml, when it should be .coverage.toml.

I requested that name originally since it sounded more familiar. The precedent is .pylintrc/.pylintrc.toml/pylintrc/pylintrc.toml.

Also, wouldn't .coverage.toml be confused with the .coverage SQLite db?

@nedbat
Copy link
Member

nedbat commented Nov 18, 2025

BTW, I hadn't really noticed until now, but this PR names the file .coveragerc.toml, when it should be .coverage.toml.

I requested that name originally since it sounded more familiar. The precedent is .pylintrc/.pylintrc.toml/pylintrc/pylintrc.toml.

Also, wouldn't .coverage.toml be confused with the .coverage SQLite db?

Good points! .coveragerc.toml it is!

@serban
Copy link

serban commented Nov 20, 2025

This is slightly off topic, but since it came up:

I prefer not to hide configuration files in my repositories. In addition to .coveragerc.toml, it would be great to support the same functionality at coveragerc.toml, without the leading dot.

I'd be happy to take my bike shedding to a new issue so as to not derail this pull request. Thanks for your effort on this!

@webknjaz
Copy link
Contributor

@serban many people prefer the opposite — not seeing many files in repo roots. That's why a lot of them almost religiously dump hundred of lines into pyproject.toml, I imagine. It may be a good idea to support both, it's a common conversation. I'm not sure if we should grow the scope of the PR, though.

assert cov.config.data_file == ".toml-data.dat"
assert cov.config.branch is True

def test_coveragerc_toml_unprefixed_syntax(self) -> None:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It'd be a good idea to also add a test making sure that unprefixed sections are ignored if present in pyproject.toml.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, thank you. I will add it and will also need to fix the linting errors

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[FR] Tool-specific toml config

6 participants